#include <stdlib.h>
#include <stdio.h>
#include "RigidBody.h"

#ifdef __APPLE__
	#include <OpenGL/OpenGL.h>
	#include <GLUT/glut.h>
#else
	#include <GL/glut.h>
#endif

// Translation or Rotation mode
namespace Mode
{
    bool translation = true;
    bool rotation = false;
}

//namespace for window
namespace W
{
    const int width = 1000;
    const int height = 700;
    const char* title = "Edward Han EECE 478";
    const float viewAngle = 45;
    const float zNear = 1;
    const float zFar = 500;
}

// namespace for View (keep track of resized pixels of width and height)
namespace V
{
    int w = W::width;
    int h = W::height;
}

// pixels from right and bottom sides of the window. ]
// so (w-107, 8) will be where the first char is displayed
namespace Name
{
	const int widthPixel = 107;
	const int heightPixel = 8;
}

// namespace for rotation
namespace R
{   
    int xRotation = 27;
    int yRotation = 55;
}

// namespace for scaling 
namespace S
{
    float scale = 1;
    const float delta = 0.05;
}

// namespace for translation 
namespace T
{
    float depth = -9.0f;
  
    float x = -0.17;
    float y = -1.2;

	const float delta = 0.1f;

	// Translation factor
	const int f = 100;

}

// namespace for mouse clicks
namespace Mode
{
    bool leftDown = false;
    bool rightDown = false;

	float lastX = 0;
	float lastY = 0;
}

namespace M
{
	RigidBody car, wall;
}

void LoadModel();
void DrawModel();
void GridMap();

// Prints name on bottom right, and mode (translation or rotation) on bottom left
void PrintName()
{
	// push teapot matrix
    glPushMatrix();


		glLoadIdentity();
		glMatrixMode(GL_PROJECTION);

		// push projection matrix
		glPushMatrix();

			// create ortho 2d matrix to render text
			glLoadIdentity();
			gluOrtho2D(0, V::w, 0, V::h);
    
			glDepthFunc (GL_ALWAYS);
    
			glColor3f (1,1,1);
        
			glDisable(GL_LIGHTING);
			
			// name will be displayed at (w-widthPixel, heightPixel, 1)
			glRasterPos3f(V::w - Name::widthPixel, Name::heightPixel, 1);
		
			/*
			char* c = "Edward Han";
			for (c; *c != '\0'; c++)
			{
				glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18, *c);
			}
			*/
			// mode will be displayed at 0, heightPixel, 1)
			glRasterPos3f(0, Name::heightPixel, 1);

			char* c1; 

			if (Mode::translation == true)
			{
				c1 = "Translation Mode";
			}
			else if (Mode::rotation == true)
			{
				c1 = "Rotation Mode";
			}

			for (c1; *c1 != '\0'; c1++)
			{
				glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18, *c1);
			}

			glDepthFunc (GL_LESS);
    
		glPopMatrix ();
    
		glMatrixMode(GL_MODELVIEW);
	glPopMatrix ();
      
    glEnable(GL_LIGHTING);
}

// Displays teapot and text
void Display()
{
	glMatrixMode(GL_MODELVIEW);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   
    glColor3f(1,1,1);
    
    glPushMatrix();
    
		glColor3f(1, 1, 1);
		glTranslatef(T::x, T::y,T::depth);
		glScalef(S::scale, S::scale, S::scale);
    
		glRotatef(R::xRotation, 1, 0, 0);
		glRotatef(R::yRotation, 0, 1, 0);
    
		DrawModel();
		GridMap();
		
    glPopMatrix();
    
    PrintName();
    
    glutSwapBuffers();
}

//setup lighting
void Init ()
{
    glClearColor(0.0, 0.0, 0.0, 1.0);
    
    glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
    
    glMatrixMode(GL_MODELVIEW);
		glShadeModel( GL_SMOOTH );
    
		glClearDepth( 1.0f );
		glEnable( GL_DEPTH_TEST );
		glDepthFunc( GL_LEQUAL );
    
		glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );
    
		GLfloat ambient[] = { 0.1, 0.1, 0.1, 1.0 };
		GLfloat diffuse[] = { 0.5, 0.5, 0.5, 1.0 };

		glLightModelfv( GL_LIGHT_MODEL_AMBIENT, ambient );
		glLightfv( GL_LIGHT0, GL_DIFFUSE, diffuse );

		glEnable( GL_LIGHT0 );
		glEnable( GL_COLOR_MATERIAL );

		glEnable(GL_LIGHTING);
}

//Handles viewport and perspective view when window is resized
void Reshape(int w, int h)
{
    V::w = w;
    V::h = h;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    
    glLoadIdentity();
    gluPerspective(W::viewAngle, (float)w / (float)h, W::zNear, W::zFar);
}

// Translates teapot based of mouse
void Translation(int x, int y)
{
    if (Mode::leftDown == true)
    {
        float diffX = x - Mode::lastX;
        float diffY = y - Mode::lastY;
        
        T::x += (float) diffX / T::f;
        T::y -= (float) diffY / T::f;
        
        Mode::lastX = x;
        Mode::lastY = y;
    }
    
    if (Mode::rightDown == true)
    {
        float diffZ = y - Mode::lastY;
        T::depth += (float) diffZ / T::f;
        
        Mode::lastX = x;
        Mode::lastY = y;
    }
}

// Rotates teapot based of mouse
void Rotation(int x, int y)
{
    if (Mode::leftDown == true)
    {
        int diffX = x - Mode::lastX;
        int diffY = y - Mode::lastY;
        
        R::xRotation += (float) diffY;
        R::yRotation += (float) diffX;
        
        // prevent integer overflow
        R::xRotation = R::xRotation % 360;
        R::yRotation = R::yRotation % 360;
        
        Mode::lastX = x;
        Mode::lastY = y;
    }
}

//changes state based on mouse click
void MouseInput(int btn, int state, int x, int y)
{
    if( btn == GLUT_LEFT_BUTTON && Mode::leftDown == false )
    {
        Mode::leftDown = true;
    }
    else if (btn == GLUT_LEFT_BUTTON && Mode::leftDown == true)
    {
        Mode::leftDown = false;
    }
    else if (btn == GLUT_RIGHT_BUTTON && Mode::rightDown == false)
    {
        Mode::rightDown = true;
    }
    else if (btn == GLUT_RIGHT_BUTTON && Mode::rightDown == true)
    {
        Mode::rightDown = false;
    }
    
    Mode::lastX = x;
    Mode::lastY = y;
    
}

// Keeps track of mouse position
void MouseMotion (int x, int y)
{
    if (Mode::rotation == true)
    {
        Rotation(x, y);
    }
    else if (Mode::translation == true)
    {
        Translation(x, y);
    }
}

// Keyboard function
void Keyboard ( unsigned char key, int mousePositionX, int mousePositionY )
{
    switch ( key )
    {
        // escape key
        case 27:
            exit ( 0 );
            break;

		case 'w':
			M::car.MoveForward(1);
			break;

		case 'a':
			M::car.Turn(5);
			break;

		case 's':
			M::car.MoveForward(-1);
			break;

		case 'd':
			M::car.Turn(-5);
			break;

		case 'q':
			M::car.SetPosition(0, 0, -5);
			break;
            
        // make object bigger
        case 'o':
            S::scale += S::delta;
            break;
            
        // make object smaller
        case 'p':
            S::scale -= S::delta;
            
            if (S::scale < 0.1)
                S::scale = 0.01;
            break;
        
		// translation mode
        case 't':
            Mode::translation = true;
            Mode::rotation = false;  
            break;
        
		// rotation mode
        case 'r':
            Mode::rotation = true;
            Mode::translation = false;
            break;
            
        default:
            break;
    }
    
}

void LoadModel()
{

#ifdef __APPLE__
	M::car.LoadModel("/Users/ehan/Documents/snowbots/wavefront/cottonSR.obj");
	M::wall.LoadModel("/Users/ehan/Documents/snowbots/wavefront/wall.obj");
#else
	M::car.LoadModel("wavefront/cottonSR.obj");
	M::wall.LoadModel("wavefront/wall.obj");
#endif

    Point carBoxCentre(0, 0.65, 0);
    M::car.SetBoundingBox(1.85, 1.4, 2.65, carBoxCentre);
	M::car.SetPosition(0, 0, 0);

	//Point wallBoxCentre(0, 0.5, 0);
	//M::wall.SetBoundingBox(2.1, 1.1, 0.6, wallBoxCentre);


}

void DrawModel()
{
	M::car.DrawModel();
	M::car.DrawBoundingBox();

	M::wall.DrawWireFrame();
	M::wall.DrawBoundingBox();
}

int main(int argc, char **argv)
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH );
    
    glutInitWindowSize(W::width, W::height);
    glutCreateWindow(W::title);

	LoadModel();
    
    glutDisplayFunc(Display);
    glutIdleFunc(Display);
    
    glutKeyboardFunc(Keyboard);
    glutMouseFunc(MouseInput);
    
    glutMotionFunc(MouseMotion);
    glutReshapeFunc(Reshape);
    
    Init();
    
    glutMainLoop();
    return 0;
}

void GridMap()
{
	glDisable(GL_LIGHTING);
    const int myNum = 50;
    const int height = 0;
    const int length = 50;
    const int depth = -5;
    
    glPushMatrix();
	
    for (int x = -myNum; x < myNum; x++)
    {
        glBegin(GL_LINE_LOOP);
        glVertex3f(x, height, -myNum);
        glVertex3f(x, height, myNum);
        glEnd();
    }
    
    for (int z = -myNum; z < myNum; z++)
    {
        glBegin(GL_LINE_LOOP);
        glVertex3f(-myNum, height, z);
        glVertex3f(myNum, height, z);
        glEnd();
    } 
    
    glColor3f(0, 0, 1);
    glBegin(GL_LINE_LOOP);
    glVertex3f(0, 0, 0);
    glVertex3f(0, 0, -length);
    glEnd();
    
    glColor3f(0, 1, 0);
    glBegin(GL_LINE_LOOP);
    glVertex3f(0, 0, 0);
    glVertex3f(0, length, 0);
    glEnd();
    
    glColor3f(1, 0, 0);
    glBegin(GL_LINE_LOOP);
    glVertex3f(0, 0, 0);
    glVertex3f(length, 0, 0);
    glEnd();
    glPopMatrix();
    
    glColor3f(1, 1, 1);
	glEnable(GL_LIGHTING);
}

